/*
 * contrib/hstore/hstore_op.c
 */
#include "postgres.h"
#include "knl/knl_variable.h"

#include "access/hash.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "utils/memutils.h"

#include "hstore.h"

/* old names for C functions */
HSTORE_POLLUTE(hstore_fetchval, fetchval);
HSTORE_POLLUTE(hstore_exists, exists);
HSTORE_POLLUTE(hstore_defined, defined);
HSTORE_POLLUTE(hstore_delete, hs_delete);
HSTORE_POLLUTE(hstore_concat, hs_concat);
HSTORE_POLLUTE(hstore_contains, hs_contains);
HSTORE_POLLUTE(hstore_contained, hs_contained);
HSTORE_POLLUTE(hstore_akeys, akeys);
HSTORE_POLLUTE(hstore_avals, avals);
HSTORE_POLLUTE(hstore_skeys, skeys);
HSTORE_POLLUTE(hstore_svals, svals);
HSTORE_POLLUTE(hstore_each, each);

/*
 * We're often finding a sequence of keys in ascending order. The
 * "lowbound" parameter is used to cache lower bounds of searches
 * between calls, based on this assumption. Pass NULL for it for
 * one-off or unordered searches.
 */
int hstoreFindKey(HStore* hs, int* lowbound, const char* key, uint32 keylen)
{
    NOT_NULL_HS(hs);
    HEntry* entries = ARRPTR(hs);
    int stopLow = lowbound ? *lowbound : 0;
    int stopHigh = HS_COUNT(hs);
    int stopMiddle;
    char* base = STRPTR(hs);

    while (stopLow < stopHigh) {
        int difference;

        stopMiddle = stopLow + (stopHigh - stopLow) / 2;

        if (HS_KEYLEN(entries, stopMiddle) == keylen)
            difference = memcmp(HS_KEY(entries, base, stopMiddle), key, keylen);
        else
            difference = (HS_KEYLEN(entries, stopMiddle) > keylen) ? 1 : -1;

        if (difference == 0) {
            if (lowbound != NULL)
                *lowbound = stopMiddle + 1;
            return stopMiddle;
        } else if (difference < 0)
            stopLow = stopMiddle + 1;
        else
            stopHigh = stopMiddle;
    }

    if (lowbound != NULL)
        *lowbound = stopLow;
    return -1;
}

Pairs* hstoreArrayToPairs(ArrayType* a, int* npairs)
{
    Datum* key_datums = NULL;
    bool* key_nulls = NULL;
    int key_count;
    Pairs* key_pairs = NULL;
    int bufsiz;
    int i, j;

    deconstruct_array(a, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &key_count);

    if (key_count == 0) {
        *npairs = 0;
        return NULL;
    }

    /*
     * A text array uses at least eight bytes per element, so any overflow in
     * "key_count * sizeof(Pairs)" is small enough for palloc() to catch.
     * However, credible improvements to the array format could invalidate
     * that assumption.  Therefore, use an explicit check rather than relying
     * on palloc() to complain.
     */
    if (key_count > int(MaxAllocSize / sizeof(Pairs)))
        ereport(ERROR,
            (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                errmsg("number of pairs (%d) exceeds the maximum allowed (%d)",
                    key_count,
                    (int)(MaxAllocSize / sizeof(Pairs)))));

    key_pairs = (Pairs*)palloc(sizeof(Pairs) * key_count);

    for (i = 0, j = 0; i < key_count; i++) {
        if (!key_nulls[i]) {
            key_pairs[j].key = VARDATA(key_datums[i]);
            key_pairs[j].keylen = VARSIZE(key_datums[i]) - VARHDRSZ;
            key_pairs[j].val = NULL;
            key_pairs[j].vallen = 0;
            key_pairs[j].needfree = 0;
            key_pairs[j].isnull = 1;
            j++;
        }
    }

    *npairs = hstoreUniquePairs(key_pairs, j, &bufsiz);

    return key_pairs;
}

PG_FUNCTION_INFO_V1(hstore_fetchval);
extern "C" Datum hstore_fetchval(PG_FUNCTION_ARGS);
Datum hstore_fetchval(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    text* key = PG_GETARG_TEXT_PP(1);
    HEntry* entries = ARRPTR(hs);
    text* out = NULL;
    int idx = hstoreFindKey(hs, NULL, VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));

    if (idx < 0 || HS_VALISNULL(entries, idx))
        PG_RETURN_NULL();

    out = cstring_to_text_with_len(HS_VAL(entries, STRPTR(hs), idx), HS_VALLEN(entries, idx));

    PG_RETURN_TEXT_P(out);
}

PG_FUNCTION_INFO_V1(hstore_exists);
extern "C" Datum hstore_exists(PG_FUNCTION_ARGS);
Datum hstore_exists(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    text* key = PG_GETARG_TEXT_PP(1);
    int idx = hstoreFindKey(hs, NULL, VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));

    PG_RETURN_BOOL(idx >= 0);
}

PG_FUNCTION_INFO_V1(hstore_exists_any);
extern "C" Datum hstore_exists_any(PG_FUNCTION_ARGS);
Datum hstore_exists_any(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    ArrayType* keys = PG_GETARG_ARRAYTYPE_P(1);
    int nkeys;
    Pairs* key_pairs = hstoreArrayToPairs(keys, &nkeys);
    int i;
    int lowbound = 0;
    bool res = false;

    /*
     * we exploit the fact that the pairs list is already sorted into strictly
     * increasing order to narrow the hstoreFindKey search; each search can
     * start one entry past the previous "found" entry, or at the lower bound
     * of the last search.
     */
    for (i = 0; i < nkeys; i++) {
        int idx = hstoreFindKey(hs, &lowbound, key_pairs[i].key, key_pairs[i].keylen);

        if (idx >= 0) {
            res = true;
            break;
        }
    }

    PG_RETURN_BOOL(res);
}

PG_FUNCTION_INFO_V1(hstore_exists_all);
extern "C" Datum hstore_exists_all(PG_FUNCTION_ARGS);
Datum hstore_exists_all(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    ArrayType* keys = PG_GETARG_ARRAYTYPE_P(1);
    int nkeys;
    Pairs* key_pairs = hstoreArrayToPairs(keys, &nkeys);
    int i;
    int lowbound = 0;
    bool res = true;

    /*
     * we exploit the fact that the pairs list is already sorted into strictly
     * increasing order to narrow the hstoreFindKey search; each search can
     * start one entry past the previous "found" entry, or at the lower bound
     * of the last search.
     */
    for (i = 0; i < nkeys; i++) {
        int idx = hstoreFindKey(hs, &lowbound, key_pairs[i].key, key_pairs[i].keylen);

        if (idx < 0) {
            res = false;
            break;
        }
    }

    PG_RETURN_BOOL(res);
}

PG_FUNCTION_INFO_V1(hstore_defined);
extern "C" Datum hstore_defined(PG_FUNCTION_ARGS);
Datum hstore_defined(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    text* key = PG_GETARG_TEXT_PP(1);
    HEntry* entries = ARRPTR(hs);
    int idx = hstoreFindKey(hs, NULL, VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));
    bool res = (idx >= 0 && !HS_VALISNULL(entries, idx));

    PG_RETURN_BOOL(res);
}

PG_FUNCTION_INFO_V1(hstore_delete);
extern "C" Datum hstore_delete(PG_FUNCTION_ARGS);
Datum hstore_delete(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    NOT_NULL_HS(hs);
    text* key = PG_GETARG_TEXT_PP(1);
    char* keyptr = VARDATA_ANY(key);
    int keylen = VARSIZE_ANY_EXHDR(key);
    HStore* out = (HStore*)palloc(VARSIZE(hs));
    char *bufs = NULL, *bufd = NULL, *ptrd = NULL;
    HEntry *es = NULL, *ed = NULL;
    int i;
    int count = HS_COUNT(hs);
    uint32 outcount = 0;

    SET_VARSIZE(out, VARSIZE(hs));
    HS_SETCOUNT(out, count); /* temporary! */

    bufs = STRPTR(hs);
    es = ARRPTR(hs);
    bufd = ptrd = STRPTR(out);
    ed = ARRPTR(out);

    uint32 dlen = VARSIZE(hs) - HS_COUNT((HStore*)(out)) * 2;
    for (i = 0; i < count; ++i) {
        int len = HS_KEYLEN(es, i);
        char* ptrs = HS_KEY(es, bufs, i);

        if (!(len == keylen && memcmp(ptrs, keyptr, keylen) == 0)) {
            int vallen = HS_VALLEN(es, i);

            HS_COPYITEM(ed, bufd, ptrd, dlen, ptrs, len, vallen, HS_VALISNULL(es, i));
            ++outcount;
        }
    }

    HS_FINALIZE(out, VARSIZE(hs), outcount, bufd, ptrd);

    PG_RETURN_POINTER(out);
}

PG_FUNCTION_INFO_V1(hstore_delete_array);
extern "C" Datum hstore_delete_array(PG_FUNCTION_ARGS);
Datum hstore_delete_array(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    NOT_NULL_HS(hs);
    HStore* out = (HStore*)palloc(VARSIZE(hs));
    int hs_count = HS_COUNT(hs);
    char *ps = NULL, *bufd = NULL, *pd = NULL;
    HEntry *es = NULL, *ed = NULL;
    int i, j;
    uint32 outcount = 0;
    ArrayType* key_array = PG_GETARG_ARRAYTYPE_P(1);
    int nkeys;
    Pairs* key_pairs = hstoreArrayToPairs(key_array, &nkeys);

    SET_VARSIZE(out, VARSIZE(hs));
    HS_SETCOUNT(out, hs_count); /* temporary! */

    ps = STRPTR(hs);
    es = ARRPTR(hs);
    bufd = pd = STRPTR(out);
    ed = ARRPTR(out);

    if (nkeys == 0) {
        /* return a copy of the input, unchanged */
        errno_t rc = memcpy_s(out, VARSIZE(hs), hs, VARSIZE(hs));
        securec_check(rc, "", "");
        HS_FIXSIZE(out, hs_count);
        HS_SETCOUNT(out, hs_count);
        PG_RETURN_POINTER(out);
    }

    /*
     * this is in effect a merge between hs and key_pairs, both of which are
     * already sorted by (keylen,key); we take keys from hs only
     */
    uint32 dlen = VARSIZE(hs) - HS_COUNT((HStore*)(out)) * 2;
    for (i = j = 0; i < hs_count;) {
        int difference;

        if (j >= nkeys)
            difference = -1;
        else {
            uint32 skeylen = HS_KEYLEN(es, i);

            if (skeylen == key_pairs[j].keylen)
                difference = memcmp(HS_KEY(es, ps, i), key_pairs[j].key, key_pairs[j].keylen);
            else
                difference = (skeylen > key_pairs[j].keylen) ? 1 : -1;
        }

        if (difference > 0)
            ++j;
        else if (difference == 0)
            ++i, ++j;
        else {
            HS_COPYITEM(ed, bufd, pd, dlen, HS_KEY(es, ps, i), HS_KEYLEN(es, i), HS_VALLEN(es, i), HS_VALISNULL(es, i));
            ++outcount;
            ++i;
        }
    }

    HS_FINALIZE(out, VARSIZE(hs), outcount, bufd, pd);

    PG_RETURN_POINTER(out);
}

PG_FUNCTION_INFO_V1(hstore_delete_hstore);
extern "C" Datum hstore_delete_hstore(PG_FUNCTION_ARGS);
Datum hstore_delete_hstore(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    HStore* hs2 = PG_GETARG_HS(1);    
    NOT_NULL_HS(hs);
    NOT_NULL_HS(hs2);
    HStore* out = (HStore*)palloc(VARSIZE(hs));
    int hs_count = HS_COUNT(hs);
    int hs2_count = HS_COUNT(hs2);
    char *ps = NULL, *ps2 = NULL, *bufd = NULL, *pd = NULL;
    HEntry *es = NULL, *es2 = NULL, *ed = NULL;
    int i, j;
    uint32 outcount = 0;

    SET_VARSIZE(out, VARSIZE(hs));
    HS_SETCOUNT(out, hs_count); /* temporary! */

    ps = STRPTR(hs);
    es = ARRPTR(hs);
    ps2 = STRPTR(hs2);
    es2 = ARRPTR(hs2);
    bufd = pd = STRPTR(out);
    ed = ARRPTR(out);

    if (hs2_count == 0) {
        /* return a copy of the input, unchanged */
        errno_t rc = memcpy_s(out, VARSIZE(hs), hs, VARSIZE(hs));
        securec_check(rc, "", "");
        HS_FIXSIZE(out, hs_count);
        HS_SETCOUNT(out, hs_count);
        PG_RETURN_POINTER(out);
    }

    /*
     * this is in effect a merge between hs and hs2, both of which are already
     * sorted by (keylen,key); we take keys from hs only; for equal keys, we
     * take the value from hs unless the values are equal
     */
    uint32 dlen = VARSIZE(hs) - HS_COUNT((HStore*)(out)) * 2;
    for (i = j = 0; i < hs_count;) {
        int difference;

        if (j >= hs2_count)
            difference = -1;
        else {
            int skeylen = HS_KEYLEN(es, i);
            int s2keylen = HS_KEYLEN(es2, j);

            if (skeylen == s2keylen)
                difference = memcmp(HS_KEY(es, ps, i), HS_KEY(es2, ps2, j), skeylen);
            else
                difference = (skeylen > s2keylen) ? 1 : -1;
        }

        if (difference > 0)
            ++j;
        else if (difference == 0) {
            uint32 svallen = HS_VALLEN(es, i);
            int snullval = HS_VALISNULL(es, i);

            if (snullval != HS_VALISNULL(es2, j) ||
                (!snullval &&
                    (svallen != HS_VALLEN(es2, j) || memcmp(HS_VAL(es, ps, i), HS_VAL(es2, ps2, j), svallen) != 0))) {
                HS_COPYITEM(ed, bufd, pd, dlen, HS_KEY(es, ps, i), HS_KEYLEN(es, i), svallen, snullval);
                ++outcount;
            }
            ++i, ++j;
        } else {
            HS_COPYITEM(ed, bufd, pd, dlen, HS_KEY(es, ps, i), HS_KEYLEN(es, i), HS_VALLEN(es, i), HS_VALISNULL(es, i));
            ++outcount;
            ++i;
        }
    }

    HS_FINALIZE(out, VARSIZE(hs), outcount, bufd, pd);

    PG_RETURN_POINTER(out);
}

PG_FUNCTION_INFO_V1(hstore_concat);
extern "C" Datum hstore_concat(PG_FUNCTION_ARGS);
Datum hstore_concat(PG_FUNCTION_ARGS)
{
    HStore* s1 = PG_GETARG_HS(0);
    HStore* s2 = PG_GETARG_HS(1);

    if (NULL == s1 && NULL == s2) {
        PG_RETURN_NULL();
    }

    if (NULL == s1) {
        PG_RETURN_POINTER(s2);
    }

    if (NULL == s2) {
        PG_RETURN_POINTER(s1);
    }

    HStore* out = (HStore*)palloc(VARSIZE(s1) + VARSIZE(s2));
    char *ps1 = NULL, *ps2 = NULL, *bufd = NULL, *pd = NULL;
    HEntry *es1 = NULL, *es2 = NULL, *ed = NULL;
    int s1idx;
    int s2idx;
    int s1count = HS_COUNT(s1);
    int s2count = HS_COUNT(s2);
    uint32 outcount = 0;
    int rc = 0;

    SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE);
    HS_SETCOUNT(out, s1count + s2count);

    if (s1count == 0) {
        /* return a copy of the input, unchanged */
        rc = memcpy_s(out, VARSIZE(out), s2, VARSIZE(s2));    
        securec_check(rc, "", "");
        HS_FIXSIZE(out, s2count);
        HS_SETCOUNT(out, s2count);
        PG_RETURN_POINTER(out);
    }

    if (s2count == 0) {
        /* return a copy of the input, unchanged */
        rc = memcpy_s(out, VARSIZE(out), s1, VARSIZE(s1));    
        securec_check(rc, "", "");
        HS_FIXSIZE(out, s1count);
        HS_SETCOUNT(out, s1count);
        PG_RETURN_POINTER(out);
    }

    ps1 = STRPTR(s1);
    ps2 = STRPTR(s2);
    bufd = pd = STRPTR(out);
    es1 = ARRPTR(s1);
    es2 = ARRPTR(s2);
    ed = ARRPTR(out);

    /*
     * this is in effect a merge between s1 and s2, both of which are already
     * sorted by (keylen,key); we take s2 for equal keys
     */
    uint32 dlen = VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE - HS_COUNT((HStore*)(out)) * 2;
    for (s1idx = s2idx = 0; s1idx < s1count || s2idx < s2count; ++outcount) {
        int difference;

        if (s1idx >= s1count)
            difference = 1;
        else if (s2idx >= s2count)
            difference = -1;
        else {
            int s1keylen = HS_KEYLEN(es1, s1idx);
            int s2keylen = HS_KEYLEN(es2, s2idx);

            if (s1keylen == s2keylen)
                difference = memcmp(HS_KEY(es1, ps1, s1idx), HS_KEY(es2, ps2, s2idx), s1keylen);
            else
                difference = (s1keylen > s2keylen) ? 1 : -1;
        }

        if (difference >= 0) {
            HS_COPYITEM(ed,
                bufd,
                pd,
                dlen,
                HS_KEY(es2, ps2, s2idx),
                HS_KEYLEN(es2, s2idx),
                HS_VALLEN(es2, s2idx),
                HS_VALISNULL(es2, s2idx));
            ++s2idx;
            if (difference == 0)
                ++s1idx;
        } else {
            HS_COPYITEM(ed,
                bufd,
                pd,
                dlen,
                HS_KEY(es1, ps1, s1idx),
                HS_KEYLEN(es1, s1idx),
                HS_VALLEN(es1, s1idx),
                HS_VALISNULL(es1, s1idx));
            ++s1idx;
        }
    }

    HS_FINALIZE(out, VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE, outcount, bufd, pd);

    PG_RETURN_POINTER(out);
}

PG_FUNCTION_INFO_V1(hstore_slice_to_array);
extern "C" Datum hstore_slice_to_array(PG_FUNCTION_ARGS);
Datum hstore_slice_to_array(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    NOT_NULL_HS(hs);
    HEntry* entries = ARRPTR(hs);
    char* ptr = STRPTR(hs);
    ArrayType* key_array = PG_GETARG_ARRAYTYPE_P(1);
    ArrayType* aout = NULL;
    Datum* key_datums = NULL;
    bool* key_nulls = NULL;
    Datum* out_datums = NULL;
    bool* out_nulls = NULL;
    int key_count;
    int i;

    deconstruct_array(key_array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &key_count);

    if (key_count == 0) {
        aout = construct_empty_array(TEXTOID);
        PG_RETURN_POINTER(aout);
    }

    out_datums = (Datum*)palloc(sizeof(Datum) * key_count);
    out_nulls = (bool*)palloc(sizeof(bool) * key_count);

    for (i = 0; i < key_count; ++i) {
        text* key = (text*)DatumGetPointer(key_datums[i]);
        int idx;

        if (key_nulls[i])
            idx = -1;
        else
            idx = hstoreFindKey(hs, NULL, VARDATA(key), VARSIZE(key) - VARHDRSZ);

        if (idx < 0 || HS_VALISNULL(entries, idx)) {
            out_nulls[i] = true;
            out_datums[i] = (Datum)0;
        } else {
            out_datums[i] =
                PointerGetDatum(cstring_to_text_with_len(HS_VAL(entries, ptr, idx), HS_VALLEN(entries, idx)));
            out_nulls[i] = false;
        }
    }

    aout = construct_md_array(out_datums,
        out_nulls,
        ARR_NDIM(key_array),
        ARR_DIMS(key_array),
        ARR_LBOUND(key_array),
        TEXTOID,
        -1,
        false,
        'i');

    PG_RETURN_POINTER(aout);
}

PG_FUNCTION_INFO_V1(hstore_slice_to_hstore);
extern "C" Datum hstore_slice_to_hstore(PG_FUNCTION_ARGS);
Datum hstore_slice_to_hstore(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    NOT_NULL_HS(hs);
    HEntry* entries = ARRPTR(hs);
    char* ptr = STRPTR(hs);
    ArrayType* key_array = PG_GETARG_ARRAYTYPE_P(1);
    HStore* out = NULL;
    int nkeys;
    Pairs* key_pairs = hstoreArrayToPairs(key_array, &nkeys);
    Pairs* out_pairs = NULL;
    int bufsiz;
    int lastidx = 0;
    int i;
    int out_count = 0;

    if (nkeys == 0) {
        out = hstorePairs(NULL, 0, 0);
        PG_RETURN_POINTER(out);
    }

    /* hstoreArrayToPairs() checked overflow */
    out_pairs = (Pairs*)palloc(sizeof(Pairs) * nkeys);
    bufsiz = 0;

    /*
     * we exploit the fact that the pairs list is already sorted into strictly
     * increasing order to narrow the hstoreFindKey search; each search can
     * start one entry past the previous "found" entry, or at the lower bound
     * of the last search.
     */

    for (i = 0; i < nkeys; ++i) {
        int idx = hstoreFindKey(hs, &lastidx, key_pairs[i].key, key_pairs[i].keylen);

        if (idx >= 0) {
            out_pairs[out_count].key = key_pairs[i].key;
            bufsiz += (out_pairs[out_count].keylen = key_pairs[i].keylen);
            out_pairs[out_count].val = HS_VAL(entries, ptr, idx);
            bufsiz += (out_pairs[out_count].vallen = HS_VALLEN(entries, idx));
            out_pairs[out_count].isnull = HS_VALISNULL(entries, idx);
            out_pairs[out_count].needfree = false;
            ++out_count;
        }
    }

    /*
     * we don't use uniquePairs here because we know that the pairs list is
     * already sorted and uniq'ed.
     */

    out = hstorePairs(out_pairs, out_count, bufsiz);

    PG_RETURN_POINTER(out);
}

PG_FUNCTION_INFO_V1(hstore_akeys);
extern "C" Datum hstore_akeys(PG_FUNCTION_ARGS);
Datum hstore_akeys(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    NOT_NULL_HS(hs);
    Datum* d = NULL;
    ArrayType* a = NULL;
    HEntry* entries = ARRPTR(hs);
    char* base = STRPTR(hs);
    int count = HS_COUNT(hs);
    int i;

    if (count == 0) {
        a = construct_empty_array(TEXTOID);
        PG_RETURN_POINTER(a);
    }

    d = (Datum*)palloc(sizeof(Datum) * count);

    for (i = 0; i < count; ++i) {
        text* item = cstring_to_text_with_len(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));

        d[i] = PointerGetDatum(item);
    }

    a = construct_array(d, count, TEXTOID, -1, false, 'i');

    PG_RETURN_POINTER(a);
}

PG_FUNCTION_INFO_V1(hstore_avals);
extern "C" Datum hstore_avals(PG_FUNCTION_ARGS);
Datum hstore_avals(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    NOT_NULL_HS(hs);
    Datum* d = NULL;
    bool* nulls = NULL;
    ArrayType* a = NULL;
    HEntry* entries = ARRPTR(hs);
    char* base = STRPTR(hs);
    int count = HS_COUNT(hs);
    int lb = 1;
    int i;

    if (count == 0) {
        a = construct_empty_array(TEXTOID);
        PG_RETURN_POINTER(a);
    }

    d = (Datum*)palloc(sizeof(Datum) * count);
    nulls = (bool*)palloc(sizeof(bool) * count);

    for (i = 0; i < count; ++i) {
        if (HS_VALISNULL(entries, i)) {
            d[i] = (Datum)0;
            nulls[i] = true;
        } else {
            text* item = cstring_to_text_with_len(HS_VAL(entries, base, i), HS_VALLEN(entries, i));

            d[i] = PointerGetDatum(item);
            nulls[i] = false;
        }
    }

    a = construct_md_array(d, nulls, 1, &count, &lb, TEXTOID, -1, false, 'i');

    PG_RETURN_POINTER(a);
}

static ArrayType* hstore_to_array_internal(HStore* hs, int ndims)
{
    NOT_NULL_HS(hs);
    HEntry* entries = ARRPTR(hs);
    char* base = STRPTR(hs);
    int count = HS_COUNT(hs);
    int out_size[2] = {0, 2};
    int lb[2] = {1, 1};
    Datum* out_datums = NULL;
    bool* out_nulls = NULL;
    int i;

    Assert(ndims < 3);

    if (count == 0 || ndims == 0)
        return construct_empty_array(TEXTOID);

    out_size[0] = count * 2 / ndims;
    out_datums = (Datum*)palloc(sizeof(Datum) * count * 2);
    out_nulls = (bool*)palloc(sizeof(bool) * count * 2);

    for (i = 0; i < count; ++i) {
        text* key = cstring_to_text_with_len(HS_KEY(entries, base, i), HS_KEYLEN(entries, i));

        out_datums[i * 2] = PointerGetDatum(key);
        out_nulls[i * 2] = false;

        if (HS_VALISNULL(entries, i)) {
            out_datums[i * 2 + 1] = (Datum)0;
            out_nulls[i * 2 + 1] = true;
        } else {
            text* item = cstring_to_text_with_len(HS_VAL(entries, base, i), HS_VALLEN(entries, i));

            out_datums[i * 2 + 1] = PointerGetDatum(item);
            out_nulls[i * 2 + 1] = false;
        }
    }

    return construct_md_array(out_datums, out_nulls, ndims, out_size, lb, TEXTOID, -1, false, 'i');
}

PG_FUNCTION_INFO_V1(hstore_to_array);
extern "C" Datum hstore_to_array(PG_FUNCTION_ARGS);
Datum hstore_to_array(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    ArrayType* out = hstore_to_array_internal(hs, 1);

    PG_RETURN_POINTER(out);
}

PG_FUNCTION_INFO_V1(hstore_to_matrix);
extern "C" Datum hstore_to_matrix(PG_FUNCTION_ARGS);
Datum hstore_to_matrix(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    ArrayType* out = hstore_to_array_internal(hs, 2);

    PG_RETURN_POINTER(out);
}

/*
 * Common initialization function for the various set-returning
 * funcs. fcinfo is only passed if the function is to return a
 * composite; it will be used to look up the return tupledesc.
 * we stash a copy of the hstore in the multi-call context in
 * case it was originally toasted. (At least I assume that's why;
 * there was no explanatory comment in the original code. --AG)
 */

static void setup_firstcall(FuncCallContext* funcctx, HStore* hs, FunctionCallInfoData* fcinfo)
{
    MemoryContext oldcontext;
    HStore* st = NULL;
    NOT_NULL_HS(hs);
    oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

    st = (HStore*)palloc(VARSIZE(hs));
    errno_t rc = memcpy_s(st, VARSIZE(hs), hs, VARSIZE(hs));
    securec_check(rc, "", "");

    funcctx->user_fctx = (void*)st;

    if (fcinfo != NULL) {
        TupleDesc tupdesc;

        /* Build a tuple descriptor for our result type */
        if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
            elog(ERROR, "return type must be a row type");

        if (tupdesc != NULL) {
            funcctx->tuple_desc = BlessTupleDesc(tupdesc);
        }
    }

    MemoryContextSwitchTo(oldcontext);
}

PG_FUNCTION_INFO_V1(hstore_skeys);
extern "C" Datum hstore_skeys(PG_FUNCTION_ARGS);
Datum hstore_skeys(PG_FUNCTION_ARGS)
{
    FuncCallContext* funcctx = NULL;
    HStore* hs = NULL;
    uint32 i;

    if (SRF_IS_FIRSTCALL()) {
        hs = PG_GETARG_HS(0);
        funcctx = SRF_FIRSTCALL_INIT();
        setup_firstcall(funcctx, hs, NULL);
    }

    funcctx = SRF_PERCALL_SETUP();
    hs = (HStore*)funcctx->user_fctx;
    i = funcctx->call_cntr;

    if (i < HS_COUNT(hs)) {
        HEntry* entries = ARRPTR(hs);
        text* item = NULL;

        item = cstring_to_text_with_len(HS_KEY(entries, STRPTR(hs), i), HS_KEYLEN(entries, i));

        SRF_RETURN_NEXT(funcctx, PointerGetDatum(item));
    }

    SRF_RETURN_DONE(funcctx);
}

PG_FUNCTION_INFO_V1(hstore_svals);
extern "C" Datum hstore_svals(PG_FUNCTION_ARGS);
Datum hstore_svals(PG_FUNCTION_ARGS)
{
    FuncCallContext* funcctx = NULL;
    HStore* hs = NULL;
    uint32 i;

    if (SRF_IS_FIRSTCALL()) {
        hs = PG_GETARG_HS(0);
        funcctx = SRF_FIRSTCALL_INIT();
        setup_firstcall(funcctx, hs, NULL);
    }

    funcctx = SRF_PERCALL_SETUP();
    hs = (HStore*)funcctx->user_fctx;
    i = funcctx->call_cntr;

    if (i < HS_COUNT(hs)) {
        HEntry* entries = ARRPTR(hs);

        if (HS_VALISNULL(entries, i)) {
            ReturnSetInfo* rsi = NULL;

            /* ugly ugly ugly. why no macro for this? */
            (funcctx)->call_cntr++;
            rsi = (ReturnSetInfo*)fcinfo->resultinfo;
            rsi->isDone = ExprMultipleResult;
            PG_RETURN_NULL();
        } else {
            text* item = NULL;

            item = cstring_to_text_with_len(HS_VAL(entries, STRPTR(hs), i), HS_VALLEN(entries, i));

            SRF_RETURN_NEXT(funcctx, PointerGetDatum(item));
        }
    }

    SRF_RETURN_DONE(funcctx);
}

PG_FUNCTION_INFO_V1(hstore_contains);
extern "C" Datum hstore_contains(PG_FUNCTION_ARGS);
Datum hstore_contains(PG_FUNCTION_ARGS)
{
    HStore* val = PG_GETARG_HS(0);
    HStore* tmpl = PG_GETARG_HS(1);
    NOT_NULL_HS(val);
    NOT_NULL_HS(tmpl);
    bool res = true;
    HEntry* te = ARRPTR(tmpl);
    char* tstr = STRPTR(tmpl);
    HEntry* ve = ARRPTR(val);
    char* vstr = STRPTR(val);
    int tcount = HS_COUNT(tmpl);
    int lastidx = 0;
    int i;

    /*
     * we exploit the fact that keys in "tmpl" are in strictly increasing
     * order to narrow the hstoreFindKey search; each search can start one
     * entry past the previous "found" entry, or at the lower bound of the
     * search
     */

    for (i = 0; res && i < tcount; ++i) {
        int idx = hstoreFindKey(val, &lastidx, HS_KEY(te, tstr, i), HS_KEYLEN(te, i));

        if (idx >= 0) {
            bool nullval = HS_VALISNULL(te, i);
            uint32 vallen = HS_VALLEN(te, i);

            if (nullval != HS_VALISNULL(ve, idx) ||
                (!nullval &&
                    (vallen != HS_VALLEN(ve, idx) || memcmp(HS_VAL(te, tstr, i), HS_VAL(ve, vstr, idx), vallen))))
                res = false;
        } else
            res = false;
    }

    PG_RETURN_BOOL(res);
}

PG_FUNCTION_INFO_V1(hstore_contained);
extern "C" Datum hstore_contained(PG_FUNCTION_ARGS);
Datum hstore_contained(PG_FUNCTION_ARGS)
{
    PG_RETURN_DATUM(DirectFunctionCall2(hstore_contains, PG_GETARG_DATUM(1), PG_GETARG_DATUM(0)));
}

PG_FUNCTION_INFO_V1(hstore_each);
extern "C" Datum hstore_each(PG_FUNCTION_ARGS);
Datum hstore_each(PG_FUNCTION_ARGS)
{
    FuncCallContext* funcctx = NULL;
    HStore* hs = NULL;
    uint32 i;

    if (SRF_IS_FIRSTCALL()) {
        hs = PG_GETARG_HS(0);
        funcctx = SRF_FIRSTCALL_INIT();
        setup_firstcall(funcctx, hs, fcinfo);
    }

    funcctx = SRF_PERCALL_SETUP();
    hs = (HStore*)funcctx->user_fctx;
    i = funcctx->call_cntr;

    if (i < HS_COUNT(hs)) {
        HEntry* entries = ARRPTR(hs);
        char* ptr = STRPTR(hs);
        Datum res, dvalues[2];
        bool nulls[2] = {false, false};
        text* item = NULL;
        HeapTuple tuple;

        item = cstring_to_text_with_len(HS_KEY(entries, ptr, i), HS_KEYLEN(entries, i));
        dvalues[0] = PointerGetDatum(item);

        if (HS_VALISNULL(entries, i)) {
            dvalues[1] = (Datum)0;
            nulls[1] = true;
        } else {
            item = cstring_to_text_with_len(HS_VAL(entries, ptr, i), HS_VALLEN(entries, i));
            dvalues[1] = PointerGetDatum(item);
        }

        tuple = heap_form_tuple(funcctx->tuple_desc, dvalues, nulls);
        res = HeapTupleGetDatum(tuple);

        SRF_RETURN_NEXT(funcctx, PointerGetDatum(res));
    }

    SRF_RETURN_DONE(funcctx);
}

/*
 * btree sort order for hstores isn't intended to be useful; we really only
 * care about equality versus non-equality.  we compare the entire string
 * buffer first, then the entry pos array.
 */

PG_FUNCTION_INFO_V1(hstore_cmp);
extern "C" Datum hstore_cmp(PG_FUNCTION_ARGS);
Datum hstore_cmp(PG_FUNCTION_ARGS)
{
    HStore* hs1 = PG_GETARG_HS(0);
    HStore* hs2 = PG_GETARG_HS(1);
    NOT_NULL_HS(hs1);
    NOT_NULL_HS(hs2);
    int hcount1 = HS_COUNT(hs1);
    int hcount2 = HS_COUNT(hs2);
    int res = 0;

    if (hcount1 == 0 || hcount2 == 0) {
        /*
         * if either operand is empty, and the other is nonempty, the nonempty
         * one is larger. If both are empty they are equal.
         */
        if (hcount1 > 0)
            res = 1;
        else if (hcount2 > 0)
            res = -1;
    } else {
        /* here we know both operands are nonempty */
        char* str1 = STRPTR(hs1);
        char* str2 = STRPTR(hs2);
        HEntry* ent1 = ARRPTR(hs1);
        HEntry* ent2 = ARRPTR(hs2);
        size_t len1 = HSE_ENDPOS(ent1[2 * hcount1 - 1]);
        size_t len2 = HSE_ENDPOS(ent2[2 * hcount2 - 1]);

        res = memcmp(str1, str2, Min(len1, len2));

        if (res == 0) {
            if (len1 > len2)
                res = 1;
            else if (len1 < len2)
                res = -1;
            else if (hcount1 > hcount2)
                res = 1;
            else if (hcount2 > hcount1)
                res = -1;
            else {
                int count = hcount1 * 2;
                int i;

                for (i = 0; i < count; ++i)
                    if (HSE_ENDPOS(ent1[i]) != HSE_ENDPOS(ent2[i]) || HSE_ISNULL(ent1[i]) != HSE_ISNULL(ent2[i]))
                        break;
                if (i < count) {
                    if (HSE_ENDPOS(ent1[i]) < HSE_ENDPOS(ent2[i]))
                        res = -1;
                    else if (HSE_ENDPOS(ent1[i]) > HSE_ENDPOS(ent2[i]))
                        res = 1;
                    else if (HSE_ISNULL(ent1[i]))
                        res = 1;
                    else if (HSE_ISNULL(ent2[i]))
                        res = -1;
                }
            }
        } else {
            res = (res > 0) ? 1 : -1;
        }
    }

    /*
     * this is a btree support function; this is one of the few places where
     * memory needs to be explicitly freed.
     */
    PG_FREE_IF_COPY(hs1, 0);
    PG_FREE_IF_COPY(hs2, 1);
    PG_RETURN_INT32(res);
}

PG_FUNCTION_INFO_V1(hstore_eq);
extern "C" Datum hstore_eq(PG_FUNCTION_ARGS);
Datum hstore_eq(PG_FUNCTION_ARGS)
{
    int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1)));

    PG_RETURN_BOOL(res == 0);
}

PG_FUNCTION_INFO_V1(hstore_ne);
extern "C" Datum hstore_ne(PG_FUNCTION_ARGS);
Datum hstore_ne(PG_FUNCTION_ARGS)
{
    int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1)));

    PG_RETURN_BOOL(res != 0);
}

PG_FUNCTION_INFO_V1(hstore_gt);
extern "C" Datum hstore_gt(PG_FUNCTION_ARGS);
Datum hstore_gt(PG_FUNCTION_ARGS)
{
    int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1)));

    PG_RETURN_BOOL(res > 0);
}

PG_FUNCTION_INFO_V1(hstore_ge);
extern "C" Datum hstore_ge(PG_FUNCTION_ARGS);
Datum hstore_ge(PG_FUNCTION_ARGS)
{
    int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1)));

    PG_RETURN_BOOL(res >= 0);
}

PG_FUNCTION_INFO_V1(hstore_lt);
extern "C" Datum hstore_lt(PG_FUNCTION_ARGS);
Datum hstore_lt(PG_FUNCTION_ARGS)
{
    int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1)));

    PG_RETURN_BOOL(res < 0);
}

PG_FUNCTION_INFO_V1(hstore_le);
extern "C" Datum hstore_le(PG_FUNCTION_ARGS);
Datum hstore_le(PG_FUNCTION_ARGS)
{
    int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1)));

    PG_RETURN_BOOL(res <= 0);
}

PG_FUNCTION_INFO_V1(hstore_hash);
extern "C" Datum hstore_hash(PG_FUNCTION_ARGS);
Datum hstore_hash(PG_FUNCTION_ARGS)
{
    HStore* hs = PG_GETARG_HS(0);
    NOT_NULL_HS(hs);
    Datum hval = hash_any((unsigned char*)VARDATA(hs), VARSIZE(hs) - VARHDRSZ);

    /*
     * this is the only place in the code that cares whether the overall
     * varlena size exactly matches the true data size; this assertion should
     * be maintained by all the other code, but we make it explicit here.
     */
    Assert(VARSIZE(hs) ==
           ((HS_COUNT(hs) != 0) ? CALCDATASIZE(HS_COUNT(hs), HSE_ENDPOS(ARRPTR(hs)[2 * HS_COUNT(hs) - 1])) : HSHRDSIZE));

    PG_FREE_IF_COPY(hs, 0);
    PG_RETURN_DATUM(hval);
}
